<script lang='ts' setup>
import { onMounted, onBeforeUnmount } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
import { MeshSurfaceSampler } from 'three/examples/jsm/math/MeshSurfaceSampler.js';
import gsap from 'gsap'
import { Pane } from 'tweakpane'
/**
 * custom shader 
 */
import startParticles_vs from "@/shader/startParticles/vertexShader.glsl?raw"
import startParticles_fs from "@/shader/startParticles/fragmentShader.glsl?raw"

import ringParticles_vs from "@/shader/ringParticles/vertexShader.glsl?raw"
import ringParticles_fs from "@/shader/ringParticles/fragmentShader.glsl?raw"

import logoParticles_vs from "@/shader/logoParticles/vertexShader.glsl?raw"
import logoParticles_fs from "@/shader/logoParticles/fragmentShader.glsl?raw"

import peopleParticles_vs from "@/shader/peopleParticles/vertexShader.glsl?raw"
import peopleParticles_fs from "@/shader/peopleParticles/fragmentShader.glsl?raw"

import sceneParticles_vs from "@/shader/sceneParticles/vertexShader.glsl?raw"
import sceneParticles_fs from "@/shader/sceneParticles/fragmentShader.glsl?raw"

/**
 * loaders
 */
const textureLoader = new THREE.TextureLoader()
const gltfLoader = new GLTFLoader()
const rgbeLoader = new RGBELoader()

/**
 * core variables
 */
let canvas: HTMLDivElement;
let renderer: THREE.WebGLRenderer;
let scene: THREE.Scene;
let camera: THREE.PerspectiveCamera;
let controls: OrbitControls;
let clock: THREE.Clock;

// models
let c16simpleModel: any;
let logoModel: any;
let peopleModel: any;
let sceneModel: any;

// textures
let aoMap: THREE.Texture;
let maskMap: THREE.Texture;

// materials
let startParticlesMaterial: THREE.ShaderMaterial;
let ringParticlesMaterial: THREE.ShaderMaterial;
let logoMaterial: THREE.ShaderMaterial;
let peopleMaterial: THREE.ShaderMaterial;
let sceneMaterial: THREE.ShaderMaterial;

// gui
let pane: Pane;

function init() {
  canvas = document.querySelector('#webgl')!
  renderer = new THREE.WebGLRenderer({
    canvas,
    antialias: true
  })
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setSize(window.innerWidth, window.innerHeight)

  scene = new THREE.Scene()
  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000)
  camera.position.set(-4.8, 2.8, 6.1)

  controls = new OrbitControls(camera, canvas)

  clock = new THREE.Clock()

  rgbeLoader.load(new URL("./texture/env_home.hdr", import.meta.url).href, (environmentMap) => {
    environmentMap.mapping = THREE.EquirectangularReflectionMapping
    scene.background = environmentMap
    scene.backgroundBlurriness = 0.5
    scene.environment = environmentMap
  })
}

function autoResize() {
  window.addEventListener('resize', () => {
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setSize(window.innerWidth, window.innerHeight)
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
  });
}

function animate() {
  const time = clock.getElapsedTime()
  pane.refresh()
  controls.update()
  renderer.render(scene!, camera!)

  ringParticlesMaterial.uniforms.uTime.value = time
  sceneMaterial.uniforms.uTime.value = time

  requestAnimationFrame(animate)
}

onMounted(async () => {
  init();
  autoResize();

  await loadResources();
  createMaterials();
  createRingMesh();
  setupMeshOrMaterials();
  setGUI();

  animate()
})


async function loadResources() {
  return Promise.all([
    gltfLoader.loadAsync(new URL('/models/c16simple.glb', import.meta.url).href),
    gltfLoader.loadAsync(new URL('/models/logo.glb', import.meta.url).href),
    gltfLoader.loadAsync(new URL('/models/people.glb', import.meta.url).href),
    gltfLoader.loadAsync(new URL('/models/scene.glb', import.meta.url).href),
    textureLoader.loadAsync(new URL('/texture/ao_floor.webp', import.meta.url).href),
    textureLoader.loadAsync(new URL("/texture/t_cell.webp", import.meta.url).href)
  ]).then((res) => {
    c16simpleModel = res[0].scene
    logoModel = res[1].scene
    peopleModel = res[2].scene
    sceneModel = res[3].scene

    aoMap = res[4]
    aoMap.wrapS = aoMap.wrapT = THREE.RepeatWrapping
    aoMap.anisotropy = 4
    aoMap.colorSpace = THREE.SRGBColorSpace

    maskMap = res[5]
  })
}



function createMaterials() {
  startParticlesMaterial = new THREE.ShaderMaterial({
    vertexShader: startParticles_vs,
    fragmentShader: startParticles_fs,
    transparent: true,
    uniforms: {
      tMask: {
        value: maskMap,
      },
      uPercent: {
        value: 0
      }
    }
  })

  ringParticlesMaterial = new THREE.ShaderMaterial({
    vertexShader: ringParticles_vs,
    fragmentShader: ringParticles_fs,
    uniforms: {
      uPercent: {
        value: 0
      },
      uVmin: {
        value: 1000
      },
      uTime: {
        value: 1
      },
      uColor: {
        value: new THREE.Color(0.44, 1, 0.78)
      },
      uColorIntensity: {
        value: 1
      }
    }
  })

  logoMaterial = new THREE.ShaderMaterial({
    vertexShader: logoParticles_vs,
    fragmentShader: logoParticles_fs,
    transparent: true,
    uniforms: {
      uColor: {
        value: new THREE.Color(0.29, 1, 0.32)
      },
      uIntensity: {
        value: 1.4
      },
      uPercent: {
        value: 0
      }
    }
  })


  peopleMaterial = new THREE.ShaderMaterial({
    vertexShader: peopleParticles_vs,
    fragmentShader: peopleParticles_fs,
    uniforms: {
      uColor0: {
        value: new THREE.Color(1, 1, 1)              // 主色调
      },
      uColor0Intensity: {
        value: 5                                     // 主色调强度
      },
      uColor1: {
        value: new THREE.Color(0.012, 1, 0.318)     // 辅助色调
      },
      uColor1Intensity: {
        value: 2                                     // 辅助色调强度
      },
      uPercent: {
        value: 0                                     // 动画进度
      },
      uTime: {
        value: 0                                     // 时间变量
      },
      uVmin: {
        value: 945                                   // 最小速度
      }
    }
  })

  sceneMaterial = new THREE.ShaderMaterial({
    vertexShader: sceneParticles_vs,
    fragmentShader: sceneParticles_fs,
    transparent: true,
    uniforms: {
      aoMap: {
        value: aoMap
      },
      uWaveIntensity: {
        value: 3
      },
      uWaveColor: {
        value: new THREE.Color(0.15, 0.52, 0.99)
      },
      uColorIntensity: {
        value: 1
      },
      uPercent: {
        value: 0
      },
      uDistance: {
        value: 554.3418143368639
      },
      uTime: {
        value: 0
      },
      uVmin: {
        value: 945
      }
    }
  })


}


let footerRing: THREE.Points;
function createRingMesh() {
  const positionData = new Float32Array(7500)
  const paramsCIndex = new Uint8Array(2500)
  const paramsCDegreeData = new Float32Array(2500)

  for (let i = 0; i < 5; i++) {
    for (let j = 0; j < 500; j++) {
      const R = 4 + i * .06
        , f = Math.PI * 2 * (j / 500)
        , D = i * 500 + j;
      positionData[D * 3] = Math.cos(f) * R,
        positionData[D * 3 + 1] = .1,
        positionData[D * 3 + 2] = Math.sin(f) * R,
        paramsCIndex[D] = i,
        paramsCDegreeData[D] = f / (Math.PI * 2)
    }
  }

  const bufferGeometry = new THREE.BufferGeometry()
  bufferGeometry.setAttribute("position", new THREE.BufferAttribute(positionData, 3))
  bufferGeometry.setAttribute("paramsCIndex", new THREE.BufferAttribute(paramsCIndex, 1))
  bufferGeometry.setAttribute("paramsCDegree", new THREE.BufferAttribute(paramsCDegreeData, 1))

  footerRing = new THREE.Points(bufferGeometry, ringParticlesMaterial)
  scene.add(footerRing)
}

function setupMeshOrMaterials() {
  handleC16CarData(c16simpleModel.getObjectByName("c16simple"))

  logoModel.getObjectByName("logo002").material = logoMaterial

  const peoplePoints = createPointsFromMesh(peopleModel.getObjectByName("props005"))

  sceneModel.getObjectByName("Mesh_0015").material = sceneMaterial

  scene.add(c16simpleModel)
  scene.add(logoModel)
  scene.add(peoplePoints)
  scene.add(sceneModel)
}

function createPointsFromMesh(mesh: THREE.Mesh) {
  // 创建表面采样器，用于在模型表面均匀采样点
  const surfaceSampler = new MeshSurfaceSampler(mesh)
    .setWeightAttribute('color')  // 使用颜色作为权重
    .build();

  // 设置粒子系统参数
  const PARTICLE_COUNT = 20000                              // 粒子总数
  const geometry = new THREE.BufferGeometry()               // 粒子几何体
  const sharedBuffer = new ArrayBuffer(PARTICLE_COUNT * 16) // 共享缓冲区
  const positionArray = new Float32Array(sharedBuffer)      // 位置数组
  const normalArray = new Float32Array(PARTICLE_COUNT * 3)  // 法线数组
  const colorArray = new Uint8Array(sharedBuffer)          // 颜色数组

  // 采样辅助向量
  const samplePosition = new THREE.Vector3()  // 采样位置
  const sampleNormal = new THREE.Vector3()    // 采样法线
  const sampleColor = new THREE.Color()       // 采样颜色

  // 在模型表面进行采样
  for (let i = 0; i < positionArray.length; i += 4) {
    // 采样一个点，获取位置、法线和颜色
    surfaceSampler.sample(samplePosition, sampleNormal, sampleColor)

    // 存储位置数据
    positionArray[i] = samplePosition.x
    positionArray[i + 1] = samplePosition.y
    positionArray[i + 2] = samplePosition.z

    // 存储法线数据
    const normalIndex = Math.floor(i / 4) * 3
    normalArray[normalIndex] = sampleNormal.x
    normalArray[normalIndex + 1] = sampleNormal.y
    normalArray[normalIndex + 2] = sampleNormal.z

    // 存储颜色数据
    const colorIndex = (i + 3) * 4
    colorArray[colorIndex] = sampleColor.r * 255     // 转换到0-255范围
    colorArray[colorIndex + 1] = sampleColor.g * 255
    colorArray[colorIndex + 2] = sampleColor.b * 255
    colorArray[colorIndex + 3] = 0                   // alpha 通道
  }

  // 创建交错缓冲区
  const positionBuffer = new THREE.InterleavedBuffer(positionArray, 4)
  const normalBuffer = new THREE.BufferAttribute(normalArray, 3)
  const colorBuffer = new THREE.InterleavedBuffer(colorArray, 16)

  geometry.setAttribute("position", new THREE.InterleavedBufferAttribute(positionBuffer, 3, 0, false))
  geometry.setAttribute("normal", normalBuffer)
  geometry.setAttribute("color", new THREE.InterleavedBufferAttribute(colorBuffer, 3, 12, true))


  return new THREE.Points(geometry, peopleMaterial)
}

function handleC16CarData(c16simple_mesh: any) {
  // 获取原始几何体数据
  const originalPositions = c16simple_mesh.geometry.attributes.position.array
  const originalIndices = c16simple_mesh.geometry.index.array
  const totalIndices = originalIndices.length

  // 创建新的几何体数据数组
  const newPositions = new Float32Array(totalIndices * 3)    // 顶点位置
  const normals = new Float32Array(totalIndices * 3)         // 法线
  const centerPoints = new Float32Array(totalIndices * 3)    // 三角形中心点
  const uvCoords = new Float32Array(totalIndices * 2)        // UV坐标
  const newIndices = new Uint16Array(totalIndices)          // 新的索引

  // 用于计算的辅助对象
  const triangle = new THREE.Triangle()
  const normalVector = new THREE.Vector3()
  const centerPoint = new THREE.Vector3()

  // 处理每个三角形
  for (let i = 0; i < originalIndices.length; i++) {
    const triangleIndex = i * 3           // 当前三角形的索引
    const positionOffset = i * 9          // 顶点位置的偏移量
    const uvOffset = i * 6                // UV坐标的偏移量

    // 1. 设置新的索引
    newIndices[triangleIndex] = triangleIndex
    newIndices[triangleIndex + 1] = triangleIndex + 1
    newIndices[triangleIndex + 2] = triangleIndex + 2

    // 2. 复制顶点位置
    // 第一个顶点
    newPositions[positionOffset] = originalPositions[originalIndices[triangleIndex] * 3]
    newPositions[positionOffset + 1] = originalPositions[originalIndices[triangleIndex] * 3 + 1]
    newPositions[positionOffset + 2] = originalPositions[originalIndices[triangleIndex] * 3 + 2]
    // 第二个顶点
    newPositions[positionOffset + 3] = originalPositions[originalIndices[triangleIndex + 1] * 3]
    newPositions[positionOffset + 4] = originalPositions[originalIndices[triangleIndex + 1] * 3 + 1]
    newPositions[positionOffset + 5] = originalPositions[originalIndices[triangleIndex + 1] * 3 + 2]
    // 第三个顶点
    newPositions[positionOffset + 6] = originalPositions[originalIndices[triangleIndex + 2] * 3]
    newPositions[positionOffset + 7] = originalPositions[originalIndices[triangleIndex + 2] * 3 + 1]
    newPositions[positionOffset + 8] = originalPositions[originalIndices[triangleIndex + 2] * 3 + 2]

    // 3. 计算三角形法线
    triangle.a.set(
      newPositions[positionOffset],
      newPositions[positionOffset + 1],
      newPositions[positionOffset + 2]
    )
    triangle.b.set(
      newPositions[positionOffset + 3],
      newPositions[positionOffset + 4],
      newPositions[positionOffset + 5]
    )
    triangle.c.set(
      newPositions[positionOffset + 6],
      newPositions[positionOffset + 7],
      newPositions[positionOffset + 8]
    )
    triangle.getNormal(normalVector)

    // 4. 存储法线数据（三个顶点共用同一个法线）
    for (let j = 0; j < 3; j++) {
      normals[positionOffset + j * 3] = normalVector.x
      normals[positionOffset + j * 3 + 1] = normalVector.y
      normals[positionOffset + j * 3 + 2] = normalVector.z
    }

    // 5. 计算并存储三角形中心点
    triangle.getMidpoint(centerPoint)
    for (let j = 0; j < 3; j++) {
      centerPoints[positionOffset + j * 3] = centerPoint.x
      centerPoints[positionOffset + j * 3 + 1] = centerPoint.y
      centerPoints[positionOffset + j * 3 + 2] = centerPoint.z
    }

    // 6. 设置UV���标
    uvCoords[uvOffset] = 0
    uvCoords[uvOffset + 1] = 0
    uvCoords[uvOffset + 2] = 1
    uvCoords[uvOffset + 3] = 0
    uvCoords[uvOffset + 4] = 0
    uvCoords[uvOffset + 5] = 1
  }

  // 创建新的几何体
  const newGeometry = new THREE.BufferGeometry()
  newGeometry.setAttribute("position", new THREE.BufferAttribute(newPositions, 3))
  newGeometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3))
  newGeometry.setAttribute("spritePos", new THREE.BufferAttribute(centerPoints, 3))
  newGeometry.setAttribute("uv", new THREE.BufferAttribute(uvCoords, 2))
  newGeometry.index = new THREE.BufferAttribute(newIndices, 1)

  // 更新模型几何体
  c16simple_mesh.geometry = newGeometry
  c16simple_mesh.material = startParticlesMaterial
}

function setGUI() {
  pane = new Pane()

  const animations: any = {
    simple: null,
    ring: null,
    logo: null,
    people: null,
    scene: null
  }

  const visibleConfig = {
    simple: false,
    ring: false,
    logo: false,
    people: false,
    scene: false
  }

  const startParticles = pane.addFolder({ title: 'simple模型粒子' })
  startParticles.addBinding(visibleConfig, 'simple').on('change', (({ value }: any) => {
    startParticlesMaterial.uniforms.uPercent.value = 0;
    if (animations.simple) {
      animations.simple.kill()
    }
    if (value) {
      animations.simple = gsap.to(startParticlesMaterial.uniforms.uPercent, { value: 1, duration: 2, ease: 'power0.inOut' })
    }
  }))
  createViewGraphGUI(startParticles, startParticlesMaterial.uniforms.uPercent, 'value')

  const ringParticles = pane.addFolder({ title: 'ring模型粒子' })
  ringParticles.addBinding(visibleConfig, 'ring').on('change', (({ value }: any) => {
    ringParticlesMaterial.uniforms.uPercent.value = 0;
    if (animations.ring) {
      animations.ring.kill()
    }
    if (value) {
      animations.ring = gsap.to(ringParticlesMaterial.uniforms.uPercent, { value: 1, duration: 2, ease: 'power0.inOut' })
    }
  }))
  createViewGraphGUI(ringParticles, ringParticlesMaterial.uniforms.uPercent, 'value')


  const logoParticles = pane.addFolder({ title: 'logo模型粒子' })
  logoParticles.addBinding(visibleConfig, 'logo').on('change', (({ value }: any) => {
    logoMaterial.uniforms.uPercent.value = 0;
    if (animations.logo) {
      animations.logo.kill()
    }
    if (value) {
      animations.logo = gsap.to(logoMaterial.uniforms.uPercent, { value: 1, duration: 2, ease: 'power0.inOut' })
    }
  }))
  createViewGraphGUI(logoParticles, logoMaterial.uniforms.uPercent, 'value')

  const peopleParticles = pane.addFolder({ title: 'people模型粒子' })
  peopleParticles.addBinding(visibleConfig, 'people').on('change', (({ value }: any) => {
    peopleMaterial.uniforms.uPercent.value = 0;
    if (animations.people) {
      animations.people.kill()
    }
    if (value) {
      animations.people = gsap.to(peopleMaterial.uniforms.uPercent, { value: 1, duration: 2, ease: 'power0.inOut' })
    }
  }))
  createViewGraphGUI(peopleParticles, peopleMaterial.uniforms.uPercent, 'value')

  const sceneParticles = pane.addFolder({ title: 'scene模型粒子' })
  sceneParticles.addBinding(visibleConfig, 'scene').on('change', (({ value }: any) => {
    sceneMaterial.uniforms.uPercent.value = 0;
    if (animations.scene) {
      animations.scene.kill()
    }
    if (value) {
      animations.scene = gsap.to(sceneMaterial.uniforms.uPercent, { value: 1, duration: 2, ease: 'power0.inOut' })
    }
  }))
  createViewGraphGUI(sceneParticles, sceneMaterial.uniforms.uPercent, 'value')


  function createViewGraphGUI(folder: any, obj: any, key: string) {
    folder.addBinding(obj, key, { min: 0, max: 1, step: 0.01 })
    folder.addBinding(obj, key, {
      readonly: true,
      view: 'graph',
      interval: 50,
      min: 0,
      max: 1,
    })
  }
}


</script>

<template>
  <canvas id="webgl">
  </canvas>
</template>

<style>
html,
body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

#app {
  width: 100%;
  height: 100%;
}

#webgl {
  width: 100%;
  height: 100%;
  display: block;
}
</style>
